#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Matplotlib 动画制作示例
演示如何创建各种类型的动画效果
"""

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation, ArtistAnimation
import matplotlib.patches as patches

# 设置中文字体
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False

def sine_wave_animation():
    """正弦波动画示例"""
    print("=== 正弦波动画示例 ===")
    
    # 创建图形和坐标轴
    fig, ax = plt.subplots(figsize=(10, 6))
    
    # 设置坐标轴
    x = np.linspace(0, 2*np.pi, 100)
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-2, 2)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('正弦波动画')
    ax.grid(True, alpha=0.3)
    
    # 初始化线对象
    line, = ax.plot([], [], 'b-', linewidth=2, label='sin(x+t)')
    ax.legend()
    
    def init():
        """初始化函数"""
        line.set_data([], [])
        return line,
    
    def animate(frame):
        """动画函数"""
        y = np.sin(x + frame * 0.1)
        line.set_data(x, y)
        return line,
    
    # 创建动画
    anim = FuncAnimation(fig, animate, init_func=init, frames=200, 
                        interval=50, blit=True, repeat=True)
    
    plt.show()
    print("正弦波动画：观察波形的移动")
    
    return anim

def scatter_animation():
    """散点图动画示例"""
    print("=== 散点图动画示例 ===")
    
    # 设置随机种子
    np.random.seed(42)
    
    # 创建图形
    fig, ax = plt.subplots(figsize=(10, 8))
    ax.set_xlim(-3, 3)
    ax.set_ylim(-3, 3)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('粒子运动动画')
    
    # 初始化粒子
    n_particles = 50
    x = np.random.randn(n_particles)
    y = np.random.randn(n_particles)
    colors = np.random.rand(n_particles)
    sizes = np.random.randint(20, 100, n_particles)
    
    # 粒子速度
    vx = (np.random.rand(n_particles) - 0.5) * 0.1
    vy = (np.random.rand(n_particles) - 0.5) * 0.1
    
    # 创建散点图
    scat = ax.scatter(x, y, c=colors, s=sizes, alpha=0.7, cmap='viridis')
    
    def animate(frame):
        """动画函数"""
        global x, y, vx, vy
        
        # 更新位置
        x += vx
        y += vy
        
        # 边界反弹
        vx = np.where(np.abs(x) > 2.8, -vx, vx)
        vy = np.where(np.abs(y) > 2.8, -vy, vy)
        x = np.clip(x, -2.8, 2.8)
        y = np.clip(y, -2.8, 2.8)
        
        # 更新散点图
        scat.set_offsets(np.column_stack((x, y)))
        return scat,
    
    # 创建动画
    anim = FuncAnimation(fig, animate, frames=1000, interval=50, blit=True)
    
    plt.show()
    print("粒子动画：观察粒子的随机运动")
    
    return anim

def bar_chart_animation():
    """柱状图动画示例"""
    print("=== 柱状图动画示例 ===")
    
    # 创建数据
    categories = ['产品A', '产品B', '产品C', '产品D', '产品E']
    n_categories = len(categories)
    
    # 创建图形
    fig, ax = plt.subplots(figsize=(10, 6))
    ax.set_ylim(0, 100)
    ax.set_xlabel('产品')
    ax.set_ylabel('销量')
    ax.set_title('产品销量动态变化')
    
    # 初始化柱状图
    bars = ax.bar(categories, [0]*n_categories, color=['red', 'green', 'blue', 'orange', 'purple'])
    
    # 在柱子上显示数值
    value_labels = []
    for bar in bars:
        label = ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1,
                       '0', ha='center', va='bottom', fontweight='bold')
        value_labels.append(label)
    
    def animate(frame):
        """动画函数"""
        # 生成新的随机数据
        np.random.seed(frame)
        values = np.random.randint(10, 90, n_categories)
        
        # 更新柱子高度
        for bar, value in zip(bars, values):
            bar.set_height(value)
        
        # 更新数值标签
        for label, bar, value in zip(value_labels, bars, values):
            label.set_position((bar.get_x() + bar.get_width()/2, bar.get_height() + 1))
            label.set_text(str(value))
        
        return bars + value_labels
    
    # 创建动画
    anim = FuncAnimation(fig, animate, frames=100, interval=200, blit=True, repeat=True)
    
    plt.show()
    print("柱状图动画：观察销量数据的动态变化")
    
    return anim

def line_plot_growing_animation():
    """线图生长动画示例"""
    print("=== 线图生长动画示例 ===")
    
    # 创建数据
    x = np.linspace(0, 4*np.pi, 200)
    y1 = np.sin(x)
    y2 = np.cos(x)
    y3 = np.sin(x) * np.exp(-x/10)
    
    # 创建图形
    fig, ax = plt.subplots(figsize=(12, 6))
    ax.set_xlim(0, 4*np.pi)
    ax.set_ylim(-1.5, 1.5)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('函数图形生长动画')
    ax.grid(True, alpha=0.3)
    
    # 初始化线对象
    line1, = ax.plot([], [], 'b-', linewidth=2, label='sin(x)')
    line2, = ax.plot([], [], 'r-', linewidth=2, label='cos(x)')
    line3, = ax.plot([], [], 'g-', linewidth=2, label='sin(x)·exp(-x/10)')
    ax.legend()
    
    def animate(frame):
        """动画函数"""
        # 计算当前显示的数据点数量
        current_frame = min(frame * 2, len(x))
        
        # 更新线条数据
        line1.set_data(x[:current_frame], y1[:current_frame])
        line2.set_data(x[:current_frame], y2[:current_frame])
        line3.set_data(x[:current_frame], y3[:current_frame])
        
        return line1, line2, line3
    
    # 创建动画
    anim = FuncAnimation(fig, animate, frames=len(x)//2, interval=50, blit=True)
    
    plt.show()
    print("生长动画：观察函数图形的逐步绘制")
    
    return anim

def heatmap_animation():
    """热力图动画示例"""
    print("=== 热力图动画示例 ===")
    
    # 创建图形
    fig, ax = plt.subplots(figsize=(8, 8))
    
    # 设置网格
    x = np.linspace(-3, 3, 50)
    y = np.linspace(-3, 3, 50)
    X, Y = np.meshgrid(x, y)
    
    # 初始化热力图
    im = ax.imshow(np.zeros_like(X), cmap='hot', interpolation='bilinear',
                   extent=[-3, 3, -3, 3], animated=True)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('热力图动画')
    
    # 添加颜色条
    cbar = plt.colorbar(im)
    cbar.set_label('温度')
    
    def animate(frame):
        """动画函数"""
        # 创建动态热力图数据
        t = frame * 0.1
        Z = np.sin(np.sqrt(X**2 + Y**2) - t) * np.exp(-0.1*(X**2 + Y**2))
        
        # 更新热力图
        im.set_array(Z)
        im.set_clim(Z.min(), Z.max())
        
        return [im]
    
    # 创建动画
    anim = FuncAnimation(fig, animate, frames=200, interval=100, blit=True)
    
    plt.show()
    print("热力图动画：观察热传播模拟")
    
    return anim

def multiple_subplots_animation():
    """多子图动画示例"""
    print("=== 多子图动画示例 ===")
    
    # 创建图形和子图
    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(12, 10))
    fig.suptitle('多子图同步动画', fontsize=16)
    
    # 准备数据
    t = np.linspace(0, 2*np.pi, 100)
    x = np.linspace(-3, 3, 50)
    y = np.linspace(-3, 3, 50)
    X, Y = np.meshgrid(x, y)
    
    # 子图1：正弦波
    ax1.set_xlim(0, 2*np.pi)
    ax1.set_ylim(-2, 2)
    ax1.set_title('正弦波')
    ax1.grid(True, alpha=0.3)
    line1, = ax1.plot([], [], 'b-', linewidth=2)
    
    # 子图2：圆形运动
    ax2.set_xlim(-2, 2)
    ax2.set_ylim(-2, 2)
    ax2.set_title('圆周运动')
    ax2.set_aspect('equal')
    ax2.grid(True, alpha=0.3)
    point, = ax2.plot([], [], 'ro', markersize=10)
    trail, = ax2.plot([], [], 'r-', alpha=0.5)
    
    # 子图3：柱状图
    categories = ['A', 'B', 'C', 'D']
    ax3.set_ylim(0, 10)
    ax3.set_title('动态柱状图')
    bars = ax3.bar(categories, [0]*4, color=['red', 'green', 'blue', 'orange'])
    
    # 子图4：热力图
    ax4.set_title('波动热力图')
    im = ax4.imshow(np.zeros_like(X), cmap='viridis', extent=[-3, 3, -3, 3])
    
    # 存储轨迹数据
    trail_x, trail_y = [], []
    
    def animate(frame):
        """动画函数"""
        phase = frame * 0.1
        
        # 子图1：正弦波
        y1 = np.sin(t + phase)
        line1.set_data(t, y1)
        
        # 子图2：圆周运动
        x_pos = np.cos(phase)
        y_pos = np.sin(phase)
        point.set_data([x_pos], [y_pos])
        
        # 更新轨迹
        trail_x.append(x_pos)
        trail_y.append(y_pos)
        if len(trail_x) > 50:  # 保持轨迹长度
            trail_x.pop(0)
            trail_y.pop(0)
        trail.set_data(trail_x, trail_y)
        
        # 子图3：柱状图
        values = 5 + 3 * np.sin(np.arange(4) + phase)
        for bar, value in zip(bars, values):
            bar.set_height(value)
        
        # 子图4：热力图
        Z = np.sin(np.sqrt(X**2 + Y**2) - phase)
        im.set_array(Z)
        
        return [line1, point, trail] + list(bars) + [im]
    
    # 创建动画
    anim = FuncAnimation(fig, animate, frames=200, interval=100, blit=True)
    
    plt.tight_layout()
    plt.show()
    print("多子图动画：观察不同类型图表的同步变化")
    
    return anim

def artist_animation_example():
    """艺术家动画示例"""
    print("=== 艺术家动画示例 ===")
    
    # 创建图形
    fig, ax = plt.subplots(figsize=(10, 6))
    
    # 准备多帧数据
    frames = []
    for i in range(50):
        # 每一帧创建新的艺术家对象
        x = np.linspace(0, 2*np.pi, 100)
        y = np.sin(x + i * 0.2)
        
        line, = ax.plot(x, y, 'b-', linewidth=2)
        title = ax.text(0.5, 0.95, f'帧 {i+1}', transform=ax.transAxes, 
                       ha='center', fontsize=14, fontweight='bold')
        
        frames.append([line, title])
    
    # 设置坐标轴
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1.5, 1.5)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('艺术家动画示例')
    ax.grid(True, alpha=0.3)
    
    # 创建艺术家动画
    anim = ArtistAnimation(fig, frames, interval=100, blit=True, repeat=True)
    
    plt.show()
    print("艺术家动画：预先计算所有帧的动画方式")
    
    return anim

def interactive_animation_controls():
    """带控制的交互式动画"""
    print("=== 交互式动画控制示例 ===")
    
    from matplotlib.widgets import Button
    
    # 创建图形
    fig, ax = plt.subplots(figsize=(10, 8))
    plt.subplots_adjust(bottom=0.2)
    
    # 创建数据
    x = np.linspace(0, 2*np.pi, 100)
    line, = ax.plot(x, np.sin(x), 'b-', linewidth=2)
    
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-2, 2)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('可控制的动画')
    ax.grid(True, alpha=0.3)
    
    # 动画状态
    animation_running = False
    current_frame = 0
    frequency = 1.0
    
    def animate(frame):
        """动画函数"""
        global current_frame
        current_frame = frame
        y = np.sin(x + frame * 0.1 * frequency)
        line.set_data(x, y)
        return line,
    
    # 创建动画对象
    anim = FuncAnimation(fig, animate, frames=200, interval=50, blit=True, repeat=True)
    anim.pause()  # 初始暂停
    
    # 按钮回调函数
    def start_pause(event):
        global animation_running
        if animation_running:
            anim.pause()
            animation_running = False
            btn_start.label.set_text('开始')
        else:
            anim.resume()
            animation_running = True
            btn_start.label.set_text('暂停')
    
    def speed_up(event):
        global frequency
        frequency *= 1.5
    
    def slow_down(event):
        global frequency
        frequency /= 1.5
    
    def reset_animation(event):
        global current_frame, frequency
        current_frame = 0
        frequency = 1.0
        anim.pause()
        animate(0)
        fig.canvas.draw()
    
    # 创建按钮
    ax_start = plt.axes([0.1, 0.05, 0.1, 0.04])
    btn_start = Button(ax_start, '开始')
    btn_start.on_clicked(start_pause)
    
    ax_speed_up = plt.axes([0.25, 0.05, 0.1, 0.04])
    btn_speed_up = Button(ax_speed_up, '加速')
    btn_speed_up.on_clicked(speed_up)
    
    ax_slow_down = plt.axes([0.4, 0.05, 0.1, 0.04])
    btn_slow_down = Button(ax_slow_down, '减速')
    btn_slow_down.on_clicked(slow_down)
    
    ax_reset = plt.axes([0.55, 0.05, 0.1, 0.04])
    btn_reset = Button(ax_reset, '重置')
    btn_reset.on_clicked(reset_animation)
    
    plt.show()
    print("交互式动画：使用按钮控制动画的播放、速度等")
    
    return anim

def save_animation_example():
    """保存动画示例"""
    print("=== 保存动画示例 ===")
    
    # 创建简单动画
    fig, ax = plt.subplots(figsize=(8, 6))
    
    x = np.linspace(0, 2*np.pi, 100)
    line, = ax.plot(x, np.sin(x), 'b-', linewidth=2)
    
    ax.set_xlim(0, 2*np.pi)
    ax.set_ylim(-1.5, 1.5)
    ax.set_xlabel('X轴')
    ax.set_ylabel('Y轴')
    ax.set_title('可保存的动画示例')
    ax.grid(True, alpha=0.3)
    
    def animate(frame):
        y = np.sin(x + frame * 0.1)
        line.set_data(x, y)
        return line,
    
    anim = FuncAnimation(fig, animate, frames=100, interval=100, blit=True)
    
    print("动画已创建，可以使用以下代码保存：")
    print("# 保存为GIF")
    print("# anim.save('animation.gif', writer='pillow', fps=10)")
    print("# 保存为MP4")
    print("# anim.save('animation.mp4', writer='ffmpeg', fps=10)")
    
    plt.show()
    
    return anim

def main():
    """主函数，演示各种动画类型"""
    print("Matplotlib 动画制作演示")
    print("=" * 50)
    print("注意：某些动画在不同环境下可能需要额外设置")
    print()
    
    # 存储动画对象，防止被垃圾回收
    animations = []
    
    try:
        # 基础动画示例
        animations.append(sine_wave_animation())
        animations.append(scatter_animation())
        animations.append(bar_chart_animation())
        animations.append(line_plot_growing_animation())
        animations.append(heatmap_animation())
        
        # 高级动画示例
        animations.append(multiple_subplots_animation())
        animations.append(artist_animation_example())
        
        # 交互式动画
        animations.append(interactive_animation_controls())
        
        # 保存动画示例
        animations.append(save_animation_example())
        
    except Exception as e:
        print(f"某些动画示例可能无法在当前环境中运行: {e}")
    
    print("\n动画演示完成！")
    print("动画制作要点：")
    print("- FuncAnimation：基于函数的动画")
    print("- ArtistAnimation：基于预计算帧的动画")
    print("- 使用blit=True可以提高性能")
    print("- 可以保存为GIF、MP4等格式")
    print("- 可以添加交互控制")
    
    # 返回动画对象列表，保持引用
    return animations

if __name__ == "__main__":
    animations = main() 